﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;


namespace SageKing.Core.Contracts.FilterTypes;

public class StringFieldFilterType : BaseFieldFilterType
{
    private static readonly MethodInfo _stringToLower = typeof(string).GetMethod(nameof(string.ToLower), []);

    public override SageKingFilterCompareOperator DefaultCompareOperator => SageKingFilterCompareOperator.Contains;

    private static readonly IEnumerable<SageKingFilterCompareOperator> _supportedCompareOperators =
    [
        SageKingFilterCompareOperator.Equals,
        SageKingFilterCompareOperator.NotEquals,
        SageKingFilterCompareOperator.Contains,
        SageKingFilterCompareOperator.NotContains,
        SageKingFilterCompareOperator.StartsWith,
        SageKingFilterCompareOperator.EndsWith,
    ];

    public StringFieldFilterType()
    {
        SupportedCompareOperators = _supportedCompareOperators;
    }

    public override Expression GetFilterExpression(SageKingFilterCompareOperator compareOperator, Expression leftExpr, Expression rightExpr)
    {
        // Helper function to convert expression to string if necessary
        Expression ConvertToStringIfNecessary(Expression expr)
        {
            if (expr.Type == typeof(object))
            {
                // Check if the actual value is a string
                var stringExpr = Expression.TypeAs(expr, typeof(string));
                var condition = Expression.NotEqual(stringExpr, Expression.Constant(null, typeof(string)));
                return Expression.Condition(condition, stringExpr, Expression.Constant(string.Empty));
            }
            return expr;
        }

        // Convert left and right expressions to string type if necessary
        var leftStringExpr = ConvertToStringIfNecessary(leftExpr);
        var rightStringExpr = ConvertToStringIfNecessary(rightExpr);

        MethodCallExpression lowerLeftExpr = Expression.Call(leftStringExpr, _stringToLower);
        MethodCallExpression lowerRightExpr = Expression.Call(rightStringExpr, _stringToLower);

        return compareOperator switch
        {
            SageKingFilterCompareOperator.IsNull or SageKingFilterCompareOperator.IsNotNull => base.GetFilterExpression(
                compareOperator, leftExpr, rightExpr),
            SageKingFilterCompareOperator.Contains => NotNullAnd(GetMethodExpression(nameof(string.Contains),
                lowerLeftExpr, lowerRightExpr)),
            SageKingFilterCompareOperator.NotContains => NotNullAnd(Expression.Not(GetMethodExpression(nameof(string.Contains),
                lowerLeftExpr, lowerRightExpr))),
            SageKingFilterCompareOperator.StartsWith => NotNullAnd(GetMethodExpression(nameof(string.StartsWith),
                lowerLeftExpr, lowerRightExpr)),
            SageKingFilterCompareOperator.EndsWith => NotNullAnd(GetMethodExpression(nameof(string.EndsWith),
                lowerLeftExpr, lowerRightExpr)),
            SageKingFilterCompareOperator.NotEquals => Expression.OrElse(
                Expression.Equal(leftExpr, Expression.Constant(null)),
                base.GetFilterExpression(compareOperator, lowerLeftExpr, lowerRightExpr)),
            _ => NotNullAnd(base.GetFilterExpression(compareOperator, lowerLeftExpr, lowerRightExpr))
        };

        Expression NotNullAnd(Expression innerExpression)
            => Expression.AndAlso(Expression.NotEqual(leftExpr, Expression.Constant(null)), innerExpression);
    }

    private static MethodCallExpression GetMethodExpression(string methodName, Expression leftExpr, Expression rightExpr)
    {
        MethodInfo mi = typeof(string).GetMethod(methodName, [typeof(string)])
            ?? throw new MissingMethodException("There is no method - " + methodName);
        return Expression.Call(leftExpr, mi, rightExpr);
    }
}
